package com.sailmi.common.tool.crypto;
import com.sailmi.common.tool.utils.StringPool;
import com.sailmi.common.tool.utils.StringUtil;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.EllipticCurve;

/**
 * SM2 工具类
 */
public class SM2Util {

	static {
		Security.addProvider(new BouncyCastleProvider());
	}

	public final static String ENCRYPT_PREFIX = "04";
	//
	/*
	 * 以下为SM2推荐曲线参数
	 */
	public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
	public final static BigInteger SM2_ECC_P = CURVE.getQ();
	public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
	public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
	public final static BigInteger SM2_ECC_N = CURVE.getOrder();
	public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
	public final static BigInteger SM2_ECC_GX = new BigInteger(
		"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
	public final static BigInteger SM2_ECC_GY = new BigInteger(
		"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
	public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
	public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
		SM2_ECC_N, SM2_ECC_H);
	public static final int CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS);
	//

	public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
	public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
		G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
	public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
		JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());

	//


	// SM2推荐曲线名称
	public static final String SM2_CURVE_NAME = "sm2p256v1";

	public static final Charset UTF_8 = Charset.forName("utf-8");

	/**
	 * 生成密钥
	 *
	 * @return
	 * @throws Exception
	 */
	public static KeyPair genKeyPair() throws Exception {
		final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
		// 获取一个椭圆曲线类型的密钥对生成器
		final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
		SecureRandom random = new SecureRandom();
		// 使用SM2的算法区域初始化密钥生成器
		kpg.initialize(sm2Spec, random);
		// 获取密钥对
		KeyPair keyPair = kpg.generateKeyPair();

		return keyPair;
	}

	/**
	 * SM2根据公钥加密 param: message 待加密内容 ， publicKey 加密公钥（BASE64编码） return:
	 * 加密信息的Base64编码
	 *
	 * @throws InvalidCipherTextException
	 */
	public static String encryptBySM2(String message, String publicKey) throws InvalidCipherTextException {
		ECDomainParameters domin = getDomain();
		// 公钥对象
		ECPublicKeyParameters pubKeyParameters = getPubKey(publicKey, domin);

		byte[] cipherBytes = new byte[0];

		cipherBytes = encrypt(SM2Engine.Mode.C1C3C2, pubKeyParameters, message.getBytes(UTF_8));

		return Base64.toBase64String(cipherBytes);
	}

	/**
	 * SM2根据私钥解密 param： cipherText 待解密密文 privateKey-私钥（BASE64编码）
	 */
	public static String decryptBySM2(String encryptedHex, String privateKeyHex) throws InvalidCipherTextException {

		if (!encryptedHex.startsWith(ENCRYPT_PREFIX)) {
			encryptedHex = ENCRYPT_PREFIX + encryptedHex;
		}

		// 1. 获取SM2曲线参数
		ECNamedCurveParameterSpec spec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(SM2_CURVE_NAME);
		ECDomainParameters domainParams = new ECDomainParameters(
			spec.getCurve(), spec.getG(), spec.getN(), spec.getH());

		// 2. 准备私钥
		BigInteger privateKeyInt = new BigInteger(privateKeyHex, 16);
		ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKeyInt, domainParams);

		// 3. 初始化SM2引擎 (使用C1C3C2模式)
		SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
		engine.init(false, privateKeyParams);

		// 4. 解码密文
		byte[] encryptedData = Hex.decode(encryptedHex);

		// 5. 解密
		byte[] decrypted = engine.processBlock(encryptedData, 0, encryptedData.length);

		try {
			return new String(decrypted, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 解析国密sm2加密密码
	 *
	 * @param rawPassword 请求时提交的原密码
	 * @param publicKey   公钥
	 * @param privateKey  私钥
	 * @return 解密后的密码
	 */
	public static String decryptPassword(String rawPassword, String publicKey, String privateKey) {
		// 其中有空则匹配失败
		if (StringUtil.isAnyBlank(publicKey, privateKey)) {
			return StringPool.EMPTY;
		}
		// 处理部分工具类加密不带04前缀的情况
		if (!StringUtil.startsWithIgnoreCase(rawPassword, ENCRYPT_PREFIX)) {
			rawPassword = ENCRYPT_PREFIX + rawPassword;
		}
		BigInteger d = new BigInteger(1, Hex.decode(privateKey));
		ECDomainParameters domin = getDomain();
		// 私钥对象
		ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(d, domin);

		// 解密密码
		String decryptPassword = null;

		try {
			byte[] ciphertext = Hex.decode(rawPassword);
			byte[] decrypted = SM2Util.decrypt(SM2Engine.Mode.C1C3C2, ecPrivateKeyParameters, ciphertext);
			decryptPassword = new String(decrypted, StandardCharsets.UTF_8); // Specify charset
		} catch (InvalidCipherTextException e) {
			throw new RuntimeException(e);
		}
		// 签名校验
		boolean isVerified = false;
		try {
			isVerified = SM2Util.verify(decryptPassword, SM2Util.sign(decryptPassword, privateKey), publicKey);
		} catch (CryptoException e) {
			throw new RuntimeException(e);
		}
		if (!isVerified) {
			return StringPool.EMPTY;
		}
		return decryptPassword;
	}


	/**
	 * 执行桌面软件传递过来的密文解密
	 * @param cipherText
	 * @param privateKey
	 * @return
	 * @throws InvalidCipherTextException
	 */
	public static String decryptPasswordEncryptByOpenSSL(String cipherText, String privateKey) throws InvalidCipherTextException {
		String encryptStrHex = cipherText;
		String privateKeyHex = privateKey;

		// 1. 准备私钥
		byte[] privateKeyBytes = Hex.decode(privateKeyHex);
		BigInteger privateKeyD = new BigInteger(1, privateKeyBytes);

		ECNamedCurveParameterSpec sm2Spec = org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec("sm2p256v1");
		ECCurve curve = sm2Spec.getCurve();
		ECDomainParameters domainParams = new ECDomainParameters(
			curve,
			sm2Spec.getG(),
			sm2Spec.getN(),
			sm2Spec.getH(),
			sm2Spec.getSeed()
		);
		ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKeyD, domainParams);

		// 2. 解析ASN.1格式的密文
		byte[] encryptedData = Hex.decode(encryptStrHex);
		ASN1Sequence sequence = ASN1Sequence.getInstance(encryptedData);

		System.out.println("ASN.1 Sequence length: " + sequence.size());
		for (int i = 0; i < sequence.size(); i++) {
			System.out.println("Element " + i + " type: " + sequence.getObjectAt(i).getClass().getSimpleName());
		}

		// 3. 提取密文组件（兼容OpenSSL EVP_PKEY_encrypt格式）
		// 格式通常为: SEQUENCE { INTEGER(x), INTEGER(y), OCTET_STRING(C3), OCTET_STRING(C2) }
		ASN1Integer x = ASN1Integer.getInstance(sequence.getObjectAt(0));
		ASN1Integer y = ASN1Integer.getInstance(sequence.getObjectAt(1));
		byte[] c3Hash = ASN1OctetString.getInstance(sequence.getObjectAt(2)).getOctets();
		byte[] c2Ciphertext = ASN1OctetString.getInstance(sequence.getObjectAt(3)).getOctets();

		// 4. 重建C1点（从x,y坐标）
		ECPoint c1Point = curve.createPoint(x.getPositiveValue(), y.getPositiveValue());
		byte[] c1Bytes = c1Point.getEncoded(false); // 不包含压缩标志

		// 5. 准备SM2引擎（使用C1C3C2模式）
		SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
		sm2Engine.init(false, privateKeyParams);

		// 6. 组合C1|C3|C2格式
		byte[] c1c3c2 = new byte[c1Bytes.length + c3Hash.length + c2Ciphertext.length];
		System.arraycopy(c1Bytes, 0, c1c3c2, 0, c1Bytes.length);
		System.arraycopy(c3Hash, 0, c1c3c2, c1Bytes.length, c3Hash.length);
		System.arraycopy(c2Ciphertext, 0, c1c3c2, c1Bytes.length + c3Hash.length, c2Ciphertext.length);

		// 7. 执行解密
		byte[] decrypted = sm2Engine.processBlock(c1c3c2, 0, c1c3c2.length);
		return new String(decrypted);
	}

	/**
	 * 根据公钥字符串创建公钥对象
	 *
	 */
	public static ECPublicKeyParameters getPubKey(String publicKey, ECDomainParameters domain) {
		ECCurve curve = domain.getCurve();
		ECPoint point = curve.decodePoint(Base64.decode(publicKey));
		ECPublicKeyParameters PublicKey = new ECPublicKeyParameters(point, domain);
		return PublicKey;
	}

	/**
	 * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T
	 *                         0009-2012]标准为C1C3C2
	 * @param pubKeyParameters 公钥
	 * @param srcData          原文
	 * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
	 * @throws InvalidCipherTextException
	 */
	public static byte[] encrypt(SM2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
		throws InvalidCipherTextException {
		SM2Engine engine = new SM2Engine(mode);
		ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
		engine.init(true, pwr);
		return engine.processBlock(srcData, 0, srcData.length);
	}

	/**
	 * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T
	 *                         0009-2012]标准为C1C3C2
	 * @param priKeyParameters 私钥
	 * @param sm2Cipher        根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
	 * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
	 * @throws InvalidCipherTextException
	 */
	public static byte[] decrypt(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
		throws InvalidCipherTextException {
		SM2Engine engine = new SM2Engine(mode);
		engine.init(false, priKeyParameters);
		return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
	}

	public static ECDomainParameters getDomain() {
		// 获取一条SM2曲线参数
		X9ECParameters x9ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
		// 构造domain参数
		ECDomainParameters domain = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
			x9ECParameters.getN(), x9ECParameters.getH());
		return domain;
	}

	/**
	 * 私钥签名
	 *
	 * @param privateKey 私钥
	 * @param content    待签名内容
	 * @return
	 */
	public static String sign(String privateKey, String content) throws CryptoException, CryptoException {
		// 待签名内容转为字节数组
		byte[] message = content.getBytes();

		BigInteger domainParameters = new BigInteger(1, Hex.decode(privateKey));
		ECDomainParameters domin = getDomain();

		// 私钥对象
		ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(domainParameters, domin);

		// 创建签名实例
		SM2Signer sm2Signer = new SM2Signer();

		// 初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
		try {
			sm2Signer.init(true,
				new ParametersWithID(
					new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")),
					Strings.toByteArray("1234567812345678")));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		sm2Signer.update(message, 0, message.length);
		// 生成签名,签名分为两部分r和s,分别对应索引0和1的数组
		byte[] signBytes = sm2Signer.generateSignature();

		String sign = Hex.toHexString(signBytes);

		return sign;
	}

	/**
	 * 验证签名
	 *
	 * @param publicKey 公钥
	 * @param content   待签名内容
	 * @param sign      签名值
	 * @return
	 */
	public static boolean verify(String publicKey, String content, String sign) {
		// 待签名内容
		byte[] message = content.getBytes();
		byte[] signData = Hex.decode(sign);

		// 获取一条SM2曲线参数
		X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
		// 构造domain参数
		ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(),
			sm2ECParameters.getN());
		// 提取公钥点
		ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
		// 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
		ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
		// 创建签名实例
		SM2Signer sm2Signer = new SM2Signer();
		ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters,
			Strings.toByteArray("1234567812345678"));
		sm2Signer.init(false, parametersWithID);
		sm2Signer.update(message, 0, message.length);
		// 验证签名结果
		boolean verify = sm2Signer.verifySignature(signData);
		return verify;
	}

	/**
	 * 获取私钥（16进制字符串，头部不带00长度共64）
	 * @param privateKey 私钥
	 * @return
	 */
	public static String getPriKeyHexString(PrivateKey privateKey){
		BCECPrivateKey s=(BCECPrivateKey)privateKey;
		String priKeyHexString = Hex.toHexString(s.getD().toByteArray());
		if(null!= priKeyHexString && priKeyHexString.length()==66 && "00".equals(priKeyHexString.substring(0,2))){
			return priKeyHexString.substring(2);
		}
		return priKeyHexString;
	}

	/**
	 * 获取公钥（16进制字符串，头部带04长度共130）
	 * @param publicKey
	 * @return
	 */
	public static String getPubKeyHexString(PublicKey publicKey){
		BCECPublicKey p=(BCECPublicKey)publicKey;
		return Hex.toHexString(p.getQ().getEncoded(false));
	}

	public static byte[] getPublicKeyDer(ECPublicKeyParameters publicKeyParams) {
		// 公钥点
		ECPoint q = publicKeyParams.getQ();
		byte[] publicKeyBytes = q.getEncoded(false); // 04 + x + y

		// 构造DER序列
		ASN1Encodable[] v = new ASN1Encodable[] {
			new ASN1Integer(1), // version
			ECNamedCurveTable.getOID("sm2p256v1"), // 曲线OID
			new DERSequence(
				new ASN1Encodable[] {
					X9ObjectIdentifiers.id_ecPublicKey,
					GMNamedCurves.getOID("sm2p256v1") // SM2曲线OID
				}
			),
			new org.bouncycastle.asn1.DERBitString(publicKeyBytes)
		};

		try {
			return new DERSequence(v).getEncoded();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	public static void main(String[] args) throws Exception {
		KeyPair keyPair = SM2Util.genKeyPair();
		byte[] pubKeyBytes = keyPair.getPublic().getEncoded();
		System.out.println("-------public key------------------------------");
		System.out.println(ByteUtils.toHexString(pubKeyBytes));
		System.out.println(Base64.toBase64String(pubKeyBytes));
		System.out.println(BCECUtil.convertECPublicKeyX509ToPEM(keyPair.getPublic().getEncoded()));
		System.out.println("-------private key------------------------------");
		System.out.println(ByteUtils.toHexString(keyPair.getPrivate().getEncoded()));
		System.out.println(Base64.toBase64String(keyPair.getPrivate().getEncoded()));
		System.out.println(BCECUtil.convertECPrivateKeyPKCS8ToPEM(keyPair.getPrivate().getEncoded()));
		//openssl生成sm2公私钥对
		//公钥
		String pu1 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEmxmSy4HOD2d2sakaJTw0QFhRGZs25umcKzmg12FAsYNjVRmtLxcbydzTMELGKpHHle//IZ0Eqx7P15IKiyoK/g==";
		//私钥
		String pr1 = "MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgilIj1dWX+977jcQOoHDQq6XtbWaazcZdJiHjXpAhKD6hRANCAASbGZLLgc4PZ3axqRolPDRAWFEZmzbm6ZwrOaDXYUCxg2NVGa0vFxvJ3NMwQsYqkceV7/8hnQSrHs/XkgqLKgr+";

		//公钥文件是x509格式，转换成ECPublicKey，提取真正的公钥
		byte[] x509pu1 = Base64.decode(pu1);
		BCECPublicKey bcecPublicKey = BCECUtil.convertX509ToECPublicKey(x509pu1);
		String s1 = ByteUtils.toHexString(bcecPublicKey.getQ().getEncoded(false));
		System.out.println("公钥：" + s1);
		//私钥文件是pkcs8格式，转换成ECPrivateKey，提取真正的私钥
		byte[] x509pr1 = Base64.decode(pr1);
		BCECPrivateKey bcecPrivateKey = BCECUtil.convertPKCS8ToECPrivateKey(x509pr1);
		String s2 = ByteUtils.toHexString(bcecPrivateKey.getD().toByteArray());
		//java会在私钥前加00，要去除
		if (s2.startsWith("00")) {
			s2 = s2.substring(2);
		}
		System.out.println("私钥：" + s2);

		System.out.println("==========测试加密解密==========");

		//String publicKey = "049b1992cb81ce0f6776b1a91a253c34405851199b36e6e99c2b39a0d76140b183635519ad2f171bc9dcd33042c62a91c795efff219d04ab1ecfd7920a8b2a0afe";
		String publicKey = s1;
		String str = "11111111111111111111111111111111111111111111111111111111111";
		String en = encryptBySM2(str, Base64.toBase64String(Hex.decode(publicKey)) );
		System.out.println("加密后：" + en);
		//String privateKey = "8a5223d5d597fbdefb8dc40ea070d0aba5ed6d669acdc65d2621e35e9021283e";
		String privateKey = s2;
		String de = decryptBySM2(en, Base64.toBase64String(Hex.decode(privateKey)) );
		System.out.println("解密后：" + de);

		System.out.println("==========测试签名验签==========");

		String sign = sign(Base64.toBase64String(Hex.decode(privateKey)), str);
		System.out.println("sign：" + sign);
		boolean flag = verify(Base64.toBase64String(Hex.decode(publicKey)), str, sign);
		System.out.println("验签结果：" + flag);
	}
}
